Make
Table of Contents
概述
编译(compile): 使用编译器, 将源代码转化为目标文件(.o).
链接(link): 把多个 .o 文件或 lib 库文件转化为可执行文件(.exe 或 .dll).
构建(build): 指用源代码生成可执行文件的整个过程, 如先编译哪个文件, 再编译哪个文件, 先链接哪个文件, 再链接哪个文件, 都属于构建的范畴.
学习 Make 的网址: GNU Make
Make
Make 根据指定的 Shell 命令进行构建. 我们应该告诉 Make, 有哪些文件要参与构建, 这些文件的依赖关系是怎样的, 当有文件变动时, 应如何重新构建它.
Makefile
Make 使用的构建规则, 都写在 Makefile 文件里面.
Makefile 文件的规则
一个 Makefile 文件内部包含了许多规则, 每条规则的形式如下:
<target> : <prerequisites> [tab] <commands>
其中, <commands> 前面的 [tab] 表示一个 tab 键, 这是规定; <target> 是必需的; <prerequisites> 和 <commands> 都是可选的, 但两者必须至少有一个.
target
一个 target 构成一条规则. 一般来说, target 就是文件名, 如果有多个文件名, 使用空格分隔
. 如:
main: main.o g++ main.o -o main main.o: main.cpp g++ -c main.cpp -o main.o
这样就会生成一个可执行文件 main.
phony target
target 除了可以是某些文件名之外, 还可以是某个操作的名字, 称为 phony target. 如:
clean: rm *.o
当我们执行 make clean
时, 就相当于执行 rm *.o
, 把所有 .o 文件删除了.
但是, 上面的写法有一些小问题. 如果当前目录下正好有一个叫做 clean 的文件, 则这条命令不会执行, 因为 Make 发现 clean 文件已经存在, 就不再重新构建了
.
解决办法是将 clean 声明为 phony target. 如下:
.PHONY: clean clean: rm *.o
这样, make 就不会去检查是否存在一个 clean 文件, 直接执行命令.
prerequisites
prerequisites 通常是一组文件名, 文件名之间用空格分隔. 如:
main: main.o scene.o layer.o g++ main.o scene.o layer.o -o main
重新构建目标 main 的情况: main.o scene.o layer.o 三个中有一个不存在, 或者有更新过(对比文件的最后修改时间和目标的时间). 如果 main.o 不存在, 则需要另外写一条规则来生成 main.o:
main.o: main.cpp g++ -c main.cpp -o main.o
commands
commands 就是一条或多条 Shell 命令, 每个命令前都必须有一个 tab 键. 注意, 不同行的 Shell 命令会在不同的 Shell 中执行, 这些 Shell 之间没有继承关系. 如:
var-lost: export foo=bar echo "foo=[$$foo]"
这样写是取不到 foo 的值的. 如果要想取到 foo 的值, 有以下几种办法:
Makefile 语法
注释 # 号
echo
make 会打印每条命令, 然后再执行. 如:
test: # this is a test!
执行 make test
后, 会显示 this is a test!
.
如果在命令的前面加上 @ 符号, 就可以关闭输出. 如:
test: @# this is a test!
在构建的过程中, 我们常常需要了解当前正在执行哪条命令, 所以一般只在注释和 echo 命令前加上 @ 符号.
通配符
如果当前目录下有 1.c, 2.c, test.c, 1.h
print: @echo *.c # 1.c, 2.c, test.c @echo ?.c # 1.c, 2.c @echo [12].* # 1.c, 1.h, 2.c
模式匹配
Make 命令允许对文件名进行类似正则运算的匹配, 主要是使用匹配符 %.
如果当前目录下有 f1.c, f2.c 两个源码文件, 需要将它们编译为对应的对象文件:
%.o: %.c
相当于:
f1.o: f1.c f2.o: f2.c
使用 % 可以将大量同类型的文件, 只用一条规则就完成构建.
变量和赋值符
自定义变量
Makefile 可以使用等号自定义变量, 调用变量时, 需要将变量放在 $() 中间.
txt = hello world test: @echo $(txt)
Shell 变量
调用 Shell 变量, 如 $HOME, 需要在 $HOME 前面再加 $ 符号进行转义. 如:
test: @echo $$HOME
= 操作符
= 无限递归, 可以使用后面的变量来定义前面的变量. 如:
foo = $(bar) bar = $(ugh) ugh = Huh? all: echo $(foo)
执行 make all
后, 将会输出 Huh?.
:= 操作符
y := $(x) bar x := foo
此时, 输出的 y 就是 bar, x 是 foo, 可以看出, y 的输出不会使用到后面才出现的变量 x.
?= 操作符
FOO ?= bar
如果 FOO 之前没有定义过, 则 FOO 的值就是 bar, 如果之前已经定义过, 则这条语句什么也不做.
+= 操作符
objects = main.o foo.o bar.o utils.o objects += another.o
此时, objects 的值是 main.o foo.o bar.o utils.o another.o
内置变量
如 $(CC) 指向当前使用的编译器, $(MAKE) 指向当前使用的 Make 工具.
自动变量
- $@: 表示规则中的目标文件集, 如果有多个目标, 则 $@ 就是这些目标的集合.
main.o: main.cpp g++ -c main.cpp -o $@ # $@ 表示 main.o
再如:
a.txt b.txt: touch $@
相当于:
a.txt: touch a.txt b.txt: touch b.txt
- $<: 表示 prerequisites 中的第一个. 如:
a.txt: b.txt c.txt cp $< $@
相当于:
a.txt: b.txt c.txt cp b.txt a.txt
- $?: 表示 prerequisites 中比 target 更新的集合.
- $^: 表示所有 prerequisites.
- $*: 匹配 % 及 % 之前的部分. 如: dir/a.foo.b, 使用的模式为 dir/a.%.b, 则 $* 表示 dir/a.foo
- $(@D): 指向 $@ 的目录名.
- $(@F): 指向 $@ 的文件名.
- $(<D): 指向 $< 的目录名.
- $(<F): 指向 $< 的文件名.
判断和循环
判断和循环的语法与 Bash 一样.
判断编译器是否为 gcc, 然后指定不同的库文件:
ifeq ($(CC), gcc) libs=$(libs_for_gcc) else libs=$(normal_libs) endif
循环:
LIST = one two three all: for i in $(LIST); do \ echo $$i; \ done # 相当于 all: for i in one two three; do \ echo $i; \ done
函数
函数格式为: $(function arguments) 或 ${function arguments}.
- shell 函数. 其参数就是 OS shell 的命令.
srcfiles := $(shell echo src/{00..99}.txt)
- wildcard 函数: 该函数的功能是扩展通配符. 如下面的例子中, 将所有 src 目录下的 txt 文件存入 srcfiles 变量.
- subst 函数: 文本替换.
$(subst arg1, arg2, text)
使用 arg2 来替换 text 中的 arg1.
- patsubst 函数: 模式匹配的替换.
$(patsubst pattern, replacement, text)
如:
$(patsubst %.c, %.o, x.c.c bar.c) # x.c.o bar.o
例子
编译 C 语言项目:
edit : main.o kbd.o command.o display.o cc -o edit main.o kbd.o command.o display.o main.o : main.c defs.h cc -c main.c kbd.o : kbd.c defs.h command.h cc -c kbd.c command.o : command.c defs.h command.h cc -c command.c display.o : display.c defs.h cc -c display.c clean : rm edit main.o kbd.o command.o display.o .PHONY: edit clean
Generated by Emacs 25.x(Org mode 8.x)
Copyright © 2014 - Pinvon - Powered by EGO